package com.shelpe.services.simbasync.controller;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;

import com.shelpe.services.simbasync.dto.CreativesDto;
import com.shelpe.services.simbasync.dto.RptCreativeDto;
import com.shelpe.services.simbasync.exception.RequestParamException;
import com.shelpe.services.simbasync.model.Creatives;
import com.shelpe.services.simbasync.model.RptAdgroupCreative;
import com.shelpe.services.simbasync.service.AuthService;
import com.shelpe.services.simbasync.service.CreativesService;
import com.shelpe.services.simbasync.service.SyncRecordService;
import com.shelpe.services.simbasync.service.UserService;
import com.shelpe.services.simbasync.service.simbaproxy.CreativeProxy;
import com.shelpe.services.simbasync.util.Util;


@RestController
@RequestMapping(value="/simbasync")
public class CreativesController {
	
	private static final Logger logger = LoggerFactory.getLogger(CreativesController.class);
	
	private static final Map<String, Date> requestPool = new HashMap<String, Date>();
	
	@Autowired
	private CreativeProxy proxy;
	
	@Autowired
	private CreativesService service;
	
	@Autowired
	private UserService user;
	
	@Autowired
	private AuthService auth;
	
	@Autowired
	private SyncRecordService syncService;
	
	private final ExecutorService executor = Executors.newFixedThreadPool(4);
	
	@RequestMapping("/tenants/{user}/adgroups/{adgroup_id}")
	@ResponseBody
	public CreativesDto syncCreativesByUser(@PathVariable("user") String user, @PathVariable("adgroup_id") Long adgroupId){
		
		boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		
		return isNumber ? syncCreatives(null, Long.valueOf(user), adgroupId) : syncCreatives(user, null, adgroupId);
	}
	
	@RequestMapping("/tenants/{user}/adgroups/{adgroup_id}/@creativesasyn")
	@ResponseBody
	public DeferredResult<CreativesDto> syncCreativesByUserAsyn(@PathVariable("user") final String user, @PathVariable("adgroup_id") final Long adgroupId){
		
		final DeferredResult<CreativesDto> result = new DeferredResult<CreativesDto>();
		final boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		CompletionService<CreativesDto> comletionService = new ExecutorCompletionService<CreativesDto>(executor); 
		
		comletionService.submit(new Callable<CreativesDto>(){
                                                                                                                                                                                                    
			@Override
			public CreativesDto call() throws Exception {
				// TODO Auto-generated method stub
				CreativesDto dto = null;
				if(isNumber){
					dto = syncCreatives(null, Long.valueOf(user), adgroupId);
				}else{
					dto = syncCreatives(null, Long.valueOf(user), adgroupId);
				}
				
				result.setResult(dto);
				
				return dto;
			}});
		
		return result;
	}
	
	
	@RequestMapping("/tenants/{user}/campaigns/{campaign_id}/adgroups/{adgroup_id}")
	@ResponseBody
	public RptCreativeDto syncRptCreativesByUser(@PathVariable("user") String user, @PathVariable("campaign_id") Long campaignId, @PathVariable("adgroup_id") Long adgroupId,
			@RequestParam(value = "start_date", required = false) String startDate, @RequestParam(value = "end_date", required = false) String endDate,
			@RequestParam(value = "record", required = false) String record){
		
		boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		
		return isNumber ? syncRptCreatives(null, Long.valueOf(user), campaignId, adgroupId, startDate, endDate, record) : syncRptCreatives(user, null, campaignId, adgroupId, startDate, endDate, record);
	}

	@RequestMapping("/tenants/{user}/campaigns/{campaign_id}/adgroups/{adgroup_id}/@rptcreativesasyn")
	@ResponseBody
	public DeferredResult<RptCreativeDto> syncRptCreativesByUserAsyn(@PathVariable("user") final String user, @PathVariable("campaign_id") final Long campaignId, @PathVariable("adgroup_id") final Long adgroupId,
			@RequestParam(value = "start_date", required = false) final String startDate, @RequestParam(value = "end_date", required = false) final String endDate,
			@RequestParam(value = "record", required = false)final String record){
		
		final DeferredResult<RptCreativeDto> result = new DeferredResult<RptCreativeDto>();
		final boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		CompletionService<RptCreativeDto> comletionService = new ExecutorCompletionService<RptCreativeDto>(executor); 
		
		comletionService.submit(new Callable<RptCreativeDto>(){
                                                                                                                                                                                                    
			@Override
			public RptCreativeDto call() throws Exception {
				// TODO Auto-generated method stub
				RptCreativeDto dto = null;
				if(isNumber){
					dto = syncRptCreatives(null, Long.valueOf(user), campaignId, adgroupId, startDate, endDate, record);
				}else{
					dto = syncRptCreatives(user, null, campaignId, adgroupId, startDate, endDate, record);
				}
				
				result.setResult(dto);
				
				return dto;
			}});
		
		return result;
	}

	/*-----------------------------------------------------------------------------*/
	
	private CreativesDto syncCreatives(String nick, Long userId, Long adgroupId){
		
		if(userId == null){
			userId = user.getUserIdByNick(nick);
		}
		
		if(nick == null){
			nick = user.getNickByUserId(userId);
		}
		
		if(nick == null || userId == null){
			logger.warn("nick or userId is null, do nothing");
			throw new RequestParamException();
		}
		logger.info(">>>syncCreatives nick {}, userId {}, adgroupId {}", nick, userId, adgroupId);
		CreativesDto dto = new CreativesDto();
		dto.setNick(nick);
		dto.setUserId(userId);
		dto.setProcess("syncCreatives");
		Date beginProcessTime = new Date();
		
		String requestParam = Util.createKey(null, userId.toString(), null, adgroupId.toString(), null);
		if(requestPool.containsKey(requestParam)){
			
			Date lastProcessDate = requestPool.get(requestParam);
			if(beginProcessTime.getTime() - lastProcessDate.getTime() > Util.MilliSecondsOfMin * 2){
				requestPool.remove(requestParam);
				logger.info(">>>syncCreatives nick {}, userId {}, adgroupId {}, process out time, restart new sync task", nick, userId, adgroupId);
			}else{
				dto.setMessage("On processing");
				logger.info(">>>syncCreatives nick {}, userId {}, adgroupId {}, request too freq", nick, userId, adgroupId);
				return dto;
			}
		}

		requestPool.put(requestParam, beginProcessTime);	
		
		String response = null;
		try{
			response = proxy.getCreativeList(nick, adgroupId);
		}catch(Exception e){
			logger.warn("Maybe something is wrong or can not find token, userId {}", userId);
		}
		
		if(response != null && response.contains("isv.invalid-permission")){//没有权限
			dto.setMessage("invalid-permission");
			requestPool.remove(requestParam);
			return dto;
		}else if(response != null && response.contains("invalid-sessionkey")){
			dto.setMessage("invalid-sessionkey");
			logger.warn("can not get any info from proxy, maybe something is wrong, nick is {}, response {}", nick, response);
			requestPool.remove(requestParam);
			return dto;
		}else if(response == null){
			dto.setMessage("get no information");
			requestPool.remove(requestParam);
			return dto;
		}
		
		List<Creatives> list = null;
		
		try{
			list = service.parseJsonToCreatives(response);	
			service.updateCreativesData(userId, list);
			
		}catch(Exception e){
			requestPool.remove(requestParam);
			throw e;
		}
		
		List<Creatives> oldListTemp = service.getCreativeList(null, adgroupId);
		
		List<Creatives> oldList = new ArrayList<Creatives>();
		
		if(oldListTemp != null){
			oldList.addAll(oldListTemp);
		}

		Map<String, List<Creatives> > splitCreatives = service.splitCreatives(list, oldList);
		dto.setAddNum(service.addCreativeList(splitCreatives.get("add")));
		dto.setUpdateNum(service.updateCreativeList(splitCreatives.get("update")));
		dto.setDeleteNum(service.deleteCreativeList(splitCreatives.get("delete")));
		
		Date endProcessTime = new Date();
		long durTime = endProcessTime.getTime() - beginProcessTime.getTime();
		dto.setDurTime(durTime);
		
		requestPool.remove(requestParam);
		
		return dto; 
	}
	

	private RptCreativeDto syncRptCreatives(String nick, Long userId, Long campaignId, Long adgroupId, String startDate, String endDate, String record){
		
		if(userId == null){
			userId = user.getUserIdByNick(nick);
		}
		
		if(nick == null){
			nick = user.getNickByUserId(userId);
		}
		
		if(nick == null || userId == null){
			logger.warn("nick or userId is null, do nothing");
			throw new RequestParamException();
		}
		logger.info(">>>syncRptCreatives nick {}, userId {}, campaignId {}, adgroupId {}", nick, userId, campaignId, adgroupId);
		RptCreativeDto dto = new RptCreativeDto();
		dto.setNick(nick);
		dto.setUserId(userId);
		dto.setProcess("syncRptCreatives");
		Date beginProcessTime = new Date();
		
		String requestParam = Util.createKey("rpt", userId.toString(), campaignId.toString(), adgroupId.toString(), endDate);
		if(requestPool.containsKey(requestParam)){
			
			Date lastProcessDate = requestPool.get(requestParam);
			if(beginProcessTime.getTime() - lastProcessDate.getTime() > Util.MilliSecondsOfMin * 5){
				requestPool.remove(requestParam);
				logger.info(">>>syncRptCreatives nick {}, userId {}, campaignId {}, adgroupId {}, process out time, restart new sync task", nick, userId, campaignId, adgroupId);
			}else{
				dto.setMessage("On processing");
				logger.info(">>>syncRptCreatives nick {}, userId {}, campaignId {}, adgroupId {}, request too freq", nick, userId, campaignId, adgroupId);
				return dto;
			}
		}

		requestPool.put(requestParam, beginProcessTime);	
		
		
		Map<String, List<Long>> dateRange = new HashMap<String, List<Long>>();
		Map<String, String> map = parseDateRange(userId, campaignId, adgroupId, startDate, endDate, dateRange);
		
		logger.info(">>> startTime {}, endTime {}, pageSize {}", map.get("startTime"), map.get("endTime"), map.get("pageSize"));
		if(map.get("startTime").compareTo(map.get("endTime")) < 1){//需要同步
			
			String subwayToken = auth.getSubwayTokenByUserId(userId);
			
			if(subwayToken == null){
				logger.warn("can not find subwaytoken, nick is {}, campaign_id is {}", nick, campaignId);
				dto.setMessage("can not find subwaytoken");
				requestPool.remove(requestParam);
				return dto;
			}
			
			String source = "1,2,4,5";
			String searchType = "SEARCH,CAT,NOSEARCH";
			int pageSize = 5000;
			pageSize = NumberUtils.toInt(map.get("pageSize"), 5000);
			
			String response = null;			
			int pageNo = 1;
			
			List<RptAdgroupCreative> listTotal = new ArrayList<RptAdgroupCreative>();
			List<RptAdgroupCreative> subList = null;
			
			try{
				
				do{
					response = proxy.getRptCreativeBase(subwayToken, nick, campaignId, adgroupId, source, searchType, map.get("startTime"), map.get("endTime"), pageSize, pageNo);
					if(response != null && response.contains("isv.invalid-permission")){//没有权限
						dto.setMessage("invalid-permission");
						requestPool.remove(requestParam);
						return dto;
					}else if(response != null && response.contains("invalid-sessionkey")){
						dto.setMessage("invalid-sessionkey");
						logger.warn("can not get any info from proxy, maybe something is wrong, nick is {}, response {}", nick, response);
						requestPool.remove(requestParam);
						return dto;
					}else if(response == null){
						break;
					}
					
					subList = service.parseJsonToRptAdgroupCreativeBase(response);
					if(subList != null && subList.size() > 0){
						listTotal.addAll(subList);
					}
				
					pageNo++;
				}while(subList != null && subList.size() == pageSize && pageSize > 0);
			
				int restSize = 0; 
				//int totalEffect = 0;
				pageNo = 1;
				
				if(listTotal != null && listTotal.size() > 0 && pageSize > 0){
					int lastDayRecordTotal = 0;
					int lastDayNoDataNum = 0;
					do{
						response = proxy.getRptCreativeEffect(subwayToken, nick, campaignId, adgroupId, source, searchType, map.get("startTime"), map.get("endTime"), pageSize, pageNo);
						Map<String, Integer> lastDayRecord = new HashMap<String, Integer>();
						restSize = service.combineRptAdgroupCreativeEffect(response, lastDayRecord, listTotal);
						lastDayRecordTotal += lastDayRecord.get("total");
						lastDayNoDataNum += lastDayRecord.get("lost");
						//totalEffect+=restSize;
						pageNo++;
					}while(restSize == pageSize);
					
					if(record != null && record.equalsIgnoreCase("yes") 
							&& lastDayRecordTotal > 0 && lastDayNoDataNum == lastDayRecordTotal){//有基础数据，没有效果数据
						
						Date lastDate = Util.getLastDayYYYYMMDD();
						long lastCustRpt = user.countRptCustomerByUserIdDate(userId, lastDate);
						if(lastCustRpt == 0){
							
							logger.warn("syncRptCreatives, base data num {}, effect data num is zero, adgroupId is {}", listTotal.size(), adgroupId);
							syncService.updateSyncRecordByProcess("syncRptCreative", 1);
							dto.setMessage("last day has no data yet");
							requestPool.remove(requestParam);
							
							return dto;
						}
					}
					service.updateRptAdgroupCreativeData(userId, listTotal);
				}
				
			}catch(Exception e){
				requestPool.remove(requestParam);
				throw e;
			}
			
			if(startDate !=null && endDate != null){
				deleteRptDataByDateRange(dateRange);
			}
				
			dto.setAddNum(service.insertRptCreativeBatch(listTotal));
		}
		
		Date endProcessTime = new Date();
		long durTime = endProcessTime.getTime() - beginProcessTime.getTime();
		
		dto.setDurTime(durTime);
		
		requestPool.remove(requestParam);
		
		return dto;
	}
	

	/************辅助函数******************/
	
	private Map<String, String> parseDateRange(Long userId, Long campaignId,Long adgroupId, String startDate, String endDate, Map<String, List<Long>> dateRange){
		
		Integer pageSize = 5000;
	
		Map<String, String> map = null;
		if(startDate == null || endDate == null){
			Date dateOld = service.getLastTimeFromRpt(userId, campaignId, adgroupId); 
			map = Util.getDateRange(dateOld);
			startDate = map.get("startTime");
			endDate = map.get("endTime");
			pageSize = NumberUtils.toInt(map.get("pageSize"), 5000);
		}
	
		if(startDate.compareTo(endDate) < 1){
			StringBuilder sb = new StringBuilder();
			sb.append(startDate);
			sb.append(",");
			sb.append(endDate);
			String range = sb.toString();
			if(dateRange.containsKey(range)){
				dateRange.get(range).add(adgroupId);
			}else{
				List<Long> idsList = new ArrayList<Long>();
				idsList.add(adgroupId);
				dateRange.put(range, idsList);
			}
		}
		
		if(map == null){
			map = new HashMap<String, String>();
			map.put("startTime", startDate);
			map.put("endTime", endDate);
			map.put("pageSize", pageSize.toString());
		}
		
		return map;
	}
	
	private void deleteRptDataByDateRange(Map<String, List<Long>> dateRange){
		
		for (Map.Entry<String, List<Long>> entry : dateRange.entrySet()){
			
			String key = entry.getKey();
			String[] range = key.split(",");
			Date startDate = new Date();
			Date endDate = startDate;
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			try {
				startDate = sdf.parse(range[0]);
				endDate = sdf.parse(range[1]);
			} catch (ParseException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		
			List<Long> adgroupIds = entry.getValue();
			service.deleteRptCreativesByDateRange(adgroupIds, startDate, endDate);
		}
	}

}